#include <stdint.h>
#include "hypercalls.h"

/*
  minimal MetaROUTER app example - network driver

  Copyright (C) 2013 Peter Lawrence

  All rights reserved.

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU Lesser General Public License version 2.1, as
  published by the Free Software Foundation.  This program is
  distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
  License for more details.

  You should have received a copy of the GNU Lesser General Public License
  along with this program; if not, see <http://www.gnu.org/licenses/>.
*/

/* the depth of packet buffer entries appears to be entirely up to the guest (and available memory) */
#define VETH_TXBUF_COUNT 4
#define VETH_RXBUF_COUNT 128

/*
Mikrotik defines the maximum packet size as 1600, but what about jumbo packets!?

there also doesn't appear to be a way to signal the MTU for a given virtual interface
presumably, the user must set this manually via the RouterOS interface?
*/
#define VETH_RXBUF_SIZE	1600

/* data structure used on command (non-network) packets */
struct ctrl_msg
{
	uint32_t cmd;
	uint16_t id;
	uint8_t hwaddr[6];
} __attribute__((packed));

/* enumeration of "cmd" field values in "struct ctrl_msg" */
#define VETH_CMD_NEWIFACE	0
#define VETH_CMD_DELIFACE	1

static volatile struct vdma_descr veth_rx_descr[VETH_RXBUF_COUNT];
static volatile struct vdma_descr veth_tx_descr[VETH_TXBUF_COUNT];

static uint8_t veth_rx_buffer[VETH_RXBUF_COUNT][VETH_RXBUF_SIZE];

static unsigned veth_cur_rx;

uint32_t veth_iftrack;

extern void peek_packet(uint16_t ifnum, uint8_t *pnt);

void vm_init_veth(void)
{
	unsigned i;

	veth_iftrack = 0;

	/* generate a linked list of transmit buffers */

	for (i = 0; i < VETH_TXBUF_COUNT; ++i) {
		veth_tx_descr[i].addr = (unsigned) 0;
		veth_tx_descr[i].size = 0;
		veth_tx_descr[i].next = (unsigned) &veth_tx_descr[i + 1];
	}
	veth_tx_descr[VETH_TXBUF_COUNT - 1].next = (unsigned) &veth_tx_descr[0];

	/* generate a linked list of receive buffers */

	for (i = 0; i < VETH_RXBUF_COUNT; ++i) {
		veth_rx_descr[i].addr = (unsigned) veth_rx_buffer[i];
		veth_rx_descr[i].size = VETH_RXBUF_SIZE;
		veth_rx_descr[i].next = (unsigned) &veth_rx_descr[i + 1];
	}
	veth_rx_descr[VETH_RXBUF_COUNT - 1].next = (unsigned) &veth_rx_descr[0];
	
	/* provide host with buffers for virtual Ethernet data queue */
	vm_create_queue(3, 3, (unsigned) &veth_tx_descr[0], (unsigned) &veth_rx_descr[0]);
}

void vm_service_veth(void)
{
	unsigned count;
	struct ctrl_msg *cmsg;
	uint16_t veth_ifnum, i;
	uint8_t *pktpnt;

	for (;;)
	{
		if (veth_rx_descr[veth_cur_rx].size & VDMA_DONE)
		{
			count = veth_rx_descr[veth_cur_rx].size & ~VDMA_DONE;
			if (count > sizeof(uint16_t))
			{
				veth_ifnum = *((uint16_t *)(veth_rx_descr[veth_cur_rx].addr));

				pktpnt = ((uint8_t *)veth_rx_descr[veth_cur_rx].addr) + 2;
				count -= 2;

				if (0 == veth_ifnum)
				{
					/* packet is a control (non-network) packet */

					if (count >= sizeof(struct ctrl_msg) )
					{
						cmsg = (struct ctrl_msg *)pktpnt;
						switch (cmsg->cmd)
						{

						case VETH_CMD_NEWIFACE: /* host has added a new interface */
							veth_iftrack |= (1UL << (cmsg->id - 1));
							break;

						case VETH_CMD_DELIFACE: /* host has removed an interface */
							veth_iftrack &= ~(1UL << (cmsg->id - 1));
							break;

						}
					}
				}
				else
				{
					/* packet is a network packet, and 
					veth_ifnum indicates which interface it came in on */

					peek_packet(veth_ifnum, pktpnt);
				}
			}

			/* free current receive buffer entry */
			veth_rx_descr[veth_cur_rx].size = VETH_RXBUF_SIZE;

			/* hop to the next receive buffer */
			veth_cur_rx = ++veth_cur_rx % VETH_RXBUF_COUNT;
		}
		else
		{
			/* there are no more receive buffers to examine, so we bail the loop */
			break;
		}
	}
}

